﻿using System;
using System.Data;
using System.Configuration;
using System.Linq;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.HtmlControls;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.Xml.Linq;

namespace Server.Utils.JSON
{
    internal class JSONTokenizer
    {
        string m_str;
        char m_ch;
        int m_readLoc;

        public JSONTokenizer(string str)
        {
            m_str = str;
            m_readLoc = 0;

            NextChar();
        }

        public JSONToken GetNextToken()
        {
            JSONToken token = new JSONToken();
            token.loc = m_readLoc;

            SkipIgnored();

            switch (m_ch)
            {
                case '{':
                    token.type = JSONTokenType.LEFT_BRACE;
                    token.value = '{';
                    NextChar();
                    break;
                case '}':
                    token.type = JSONTokenType.RIGHT_BRACE;
                    token.value = '}';
                    NextChar();
                    break;
                case '[':
                    token.type = JSONTokenType.LEFT_BRACKET;
                    token.value = '[';
                    NextChar();
                    break;
                case ']':
                    token.type = JSONTokenType.RIGHT_BRAKCET;
                    token.value = ']';
                    NextChar();
                    break;
                case ',':
                    token.type = JSONTokenType.COMMA;
                    token.value = ',';
                    NextChar();
                    break;
                case ':':
                    token.type = JSONTokenType.COLON;
                    token.value = ':';
                    NextChar();
                    break;
                case 't':
                    string strTrue = "t" + NextChar() + NextChar() + NextChar();
                    if (strTrue == "true")
                    {
                        token.type = JSONTokenType.BOOLEAN;
                        token.value = true;
                    }
                    else
                    {
                        throw new JSONParseException(token.loc, "Expecting 'true' but found " + strTrue);
                    }
                    break;
                case 'f':
                    string strFalse = "f" + NextChar() + NextChar() + NextChar() + NextChar();
                    if (strFalse == "false")
                    {
                        token.type = JSONTokenType.BOOLEAN;
                        token.value = false;
                    }
                    else
                    {
                        throw new JSONParseException(token.loc, "Expecting 'false' but found " + strFalse);
                    }
                    break;
                case 'n':
                    string strNull = "n" + NextChar() + NextChar() + NextChar();
                    if (strNull == "null")
                    {
                        token.type = JSONTokenType.NULL;
                        token.value = null;
                    }
                    else
                    {
                        throw new JSONParseException(token.loc, "Expecting 'null' but found +" + strNull);
                    }
                    break;
                case '"':
                    token = ReadString();
                    break;
                default:
                    if (IsDigit(m_ch) || m_ch == '-')
                    {
                        token = ReadNumber();
                    }
                    else if (m_ch == '\0')
                    {
                        token.type = JSONTokenType.END;
                    }
                    else
                    {
                        throw new JSONParseException(token.loc, "Unexpected " + m_ch + " encountered");
                    }
                    break;
            }

            return token;
        }

        private char NextChar()
        {
            m_ch = m_str[m_readLoc++];
            return m_ch;
        }

        private JSONToken ReadString()
        {
            JSONToken token = new JSONToken();
            token.type = JSONTokenType.STRING;
            token.loc = m_readLoc;

            string value = "";

            NextChar();

            while (m_ch != '"' && m_ch != '\0')
            {
                if (m_ch == '\\')
                {
                    NextChar();

                    switch (m_ch)
                    {
                        case '"':
                            value += '"';
                            break;
                        case '/':
                            value += "/";
                            break;
                        case '\\':
                            value += '\\';
                            break;
                        case 'b':
                            value += '\b';
                            break;
                        case 'f':
                            value += '\f';
                            break;
                        case 'n':
                            value += '\n';
                            break;
                        case 'r':
                            value += '\r';
                            break;
                        case 't':
                            value += '\t';
                            break;
                        case 'u':
                            string hexValue = "";
                            for (int i = 0; i < 4; ++i)
                            {
                                NextChar();
                                if (!IsHexDigit(m_ch))
                                {
                                    throw new JSONParseException(m_readLoc, "Excepted a hex digit, but found '" + m_ch + "'");
                                }
                                hexValue += m_ch;
                            }

                            value += Convert.ToInt32(hexValue, 16).ToString();
                            break;
                        default:
                            value += '\\' + m_ch;
                            break;
                    }
                }
                else
                {
                    value += m_ch;
                }

                NextChar();
            }

            if (m_ch != '\0')
            {
                throw new JSONParseException(m_readLoc, "Unterminated string literal");
            }

            NextChar();
            token.value = value;

            return token;
        }

        private JSONToken ReadNumber()
        {
            JSONToken token = new JSONToken();
            token.type = JSONTokenType.NUMBER;
            token.loc = m_readLoc;
            string num = "";

            if (m_ch == '-')
            {
                num += m_ch;
                NextChar();
            }

            if (!IsDigit(m_ch))
            {
                throw new JSONParseException(m_readLoc, "Expecting a digit");
            }

            if (m_ch == '0')
            {
                num += m_ch;
                NextChar();

                if (IsDigit(m_ch))
                {
                    throw new JSONParseException(m_readLoc, "A digit cannot immediately follow 0");
                }
            }
            else
            {
                while (IsDigit(m_ch))
                {
                    num += m_ch;
                    NextChar();
                }
            }

            if (m_ch == '.')
            {
                num += '.';
                NextChar();

                if (!IsDigit(m_ch))
                {
                    throw new JSONParseException(m_readLoc, "Expecting a digit");
                }

                while (IsDigit(m_ch))
                {
                    num += m_ch;
                    NextChar();
                }
            }

            if (m_ch == 'e' || m_ch == 'E')
            {
                num += 'e';
                NextChar();

                if (m_ch == '+' || m_ch == '-')
                {
                    num += m_ch;
                    NextChar();
                }

                if (!IsDigit(m_ch))
                {
                    throw new JSONParseException(m_readLoc, "Scientific notation number need exponent value");
                }

                while (IsDigit(m_ch))
                {
                    num += m_ch;
                    NextChar();
                }
            }

            try
            {
                token.value = Convert.ToDouble(num);
            }
            catch (System.Exception)
            {
                throw new JSONParseException(m_readLoc, "Number " + num + " is not valid");
            }

            return token;
        }

        private void SkipIgnored()
        {
            SkipWhite();
            SkipComments();
            SkipWhite();
        }

        private void SkipComments()
        {
            if (m_ch == '/')
            {
                NextChar();
                switch (m_ch)
                {
                    case '/':
                        do 
                        {
                            NextChar();
                        } while (m_ch != '\n' && m_ch != '\0');

                        NextChar();
                	    break;
                    case '*':
                        NextChar();
                        while (true)
                        {
                            if (m_ch == '/')
                            {
                                NextChar();
                                break;
                            }
                            else
                            {
                                NextChar();
                            }

                            if (m_ch == '\0')
                            {
                                throw new JSONParseException(m_readLoc, "Multi-line comment not closed");
                            }
                        }
                        break;
                    default:
                        throw new JSONParseException(m_readLoc, "Unexpected" + m_ch + " encountered (expecting '/' or '*')");
                }
            }
        }

        private void SkipWhite()
        {
            while (IsWhiteSpace(m_ch))
            {
                NextChar();
            }
        }

        private bool IsWhiteSpace(char ch)
        {
            return ch == ' ' || ch == '\t' || ch == '\n';
        }

        private bool IsDigit(char ch)
        {
            return ch >= '0' && ch <= '9';
        }

        private bool IsHexDigit(char ch)
        {
            char uc = Char.ToUpper(ch);

            return IsDigit(ch) || (uc >= 'A' && uc <= 'F');
        }
    }
}